//Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using Eas.WebCrawler.Interfaces;
using System.Text.RegularExpressions;

namespace Eas.WebCrawler.PlugIns.LinkExtractors {

    /// <summary>This class collects a regular expression and a group name together in order to allow the <see cref="RegularExpressionLinkExtractor"/> class to find links on a page.</summary>
    /// <remarks>Sometimes, a regular expression might find a link like an 'a' tag which has an 'href' attribute, but the entire expression is bigger than just the link itself.
    /// In these cases, the regular expression contains an embedded group which denotes just the link itself.  This class wraps an expression and the name of the target group together in one place.
    /// If the GroupName is <code>null</code> or not provided, then the entire expression match is considered the link.</remarks>
    public class RegexSpecification {

        /// <summary>The options to use for compiling regex expressions.</summary>
        public const System.Text.RegularExpressions.RegexOptions PredefinedOptions =
                    System.Text.RegularExpressions.RegexOptions.Compiled |
                    System.Text.RegularExpressions.RegexOptions.CultureInvariant |
                    System.Text.RegularExpressions.RegexOptions.Multiline |
                    System.Text.RegularExpressions.RegexOptions.IgnoreCase;


        /// <summary>The regex expression used to match links.</summary>
        /// <remarks>Backing field for the Expression property.</remarks>
        /// <seealso cref="Expression"/>
        private Regex _Expression;

        /// <summary>The regex expression used to match links.</summary>
        public Regex Expression {
            get { return _Expression; }
            set { _Expression = value; }
        }

        /// <summary>The name of the group in the regex match that contains the link, or <code>null</code> if no group names were used to identify the link.</summary>
        /// <remarks>Backing field for the GroupName property.</remarks>
        /// <seealso cref="GroupName"/>
        private string _GroupName;

        /// <summary>Some description</summary>
        public string GroupName {
            get { return _GroupName; }
            set { _GroupName = value; }
        }

        /// <summary>Constructs a regular expression specification out of a string.</summary>
        /// <param name="expression">The string representation of an expression.</param>
        /// <remarks>The expression is constructed with the options defined by <see cref="PredefinedOptions"/>.</remarks>
        public RegexSpecification(string expression) : this(new Regex(expression, PredefinedOptions)) { }

        /// <summary>Constructs a regular expression specification out of a string and a group name.</summary>
        /// <param name="expression">The string representation of an expression.</param>
        /// <param name="groupName">The name of the <see cref="System.Text.RegularExpressions.Group"/> used to extract the links from the regular expression.</param>
        /// <remarks>The expression is constructed with the options defined by <see cref="PredefinedOptions"/>.</remarks>
        public RegexSpecification(string expression, string groupName) : this(new Regex(expression, PredefinedOptions), groupName) { }

        /// <summary>Constructs a regular expression specification out of a <see cref="Regex"/>.</summary>
        /// <param name="expression">The <see cref="Regex"/> to use.</param>
        public RegexSpecification(Regex expression) : this(expression, null) { }

        /// <summary>Constructs a regular expression specification out of a <see cref="Regex"/> and a group name.</summary>
        /// <param name="expression">The <see cref="Regex"/> to use.</param>
        /// <param name="groupName">The name of the <see cref="System.Text.RegularExpressions.Group"/> used to extract the links from the regular expression.</param>
        public RegexSpecification(Regex expression, string groupName) {
            Expression = expression;
            GroupName = groupName; 
        }
    }

    /// <summary>Base class for using regular expressions to extracts links from the responses to web requets.</summary>
    public class RegularExpressionLinkExtractor : Eas.WebCrawler.Interfaces.LinkExtractor {

        private RegexSpecification[] _Expressions;

        /// <summary>Builds a <see cref="LinkExtractor"/> out of a collection of regular expressions.</summary>
        /// <param name="regularExpressions">A set of regular expression specifications that provide details about the regular expression.</param>
        public RegularExpressionLinkExtractor(params RegexSpecification[] regularExpressions) {
            _Expressions = regularExpressions;
        }

        #region LinkExtractor Members

        /// <summary>Returns a collection of links from a page by applying this class' contained regular expressions to the page and collecting the results.</summary>
        /// <param name="processingState">The details of the current request.</param>
        /// <param name="reader">The reader containing the body of the request.</param>
        /// <returns>An enumeration of <see cref="Uri"/>s found in the body of the web response.</returns>
        public IEnumerable<Uri> GetLinks(Eas.WebCrawler.Interfaces.ProcessingState processingState, System.IO.TextReader reader) {
            string text = reader.ReadToEnd();
            foreach (RegexSpecification expression in _Expressions) {
                foreach (Uri uri in this.GetLinksFromSingleExpression(text, expression)) {
                    yield return uri;
                }
            }
        }

        #endregion

        private IEnumerable<Uri> GetLinksFromSingleExpression(string text, RegexSpecification expression) {
            System.Text.RegularExpressions.MatchCollection matches = expression.Expression.Matches(text);
            foreach(string textUri in GetMatches(expression.GroupName ,matches)){
                System.Uri uri;
                if (Uri.TryCreate(textUri, UriKind.RelativeOrAbsolute, out uri))
                    yield return uri;
            }
        }

        private IEnumerable<string> GetMatches(string groupName, System.Text.RegularExpressions.MatchCollection matches) {
            foreach (System.Text.RegularExpressions.Match match in matches) {
                if (groupName == null)
                    yield return match.Value;
                else
                    yield return match.Groups[groupName].Value;
            }
        }

        #region WebCrawlerComponent Members
        /// <summary>Initializes this class</summary>
        /// <param name="context">The <see cref="WebCrawlerContext"/> which will contain data about this instance of the web crawler.</param>
        public void Initialize(WebCrawlerContext context) {}
        #endregion
    }
}
